/*
 * code generator for runtime LPC code
 */
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>
#include "macros.h"
#include "opcodes.h"
#include "compiler.h"
#include "program.h"
#include "interpret.h"
#include "simulate.h"
#include "icode.h"

static void ins_real(double);
static void ins_short(short);
static void upd_short(int, int, char *);
static void ins_byte(unsigned char);
static void upd_byte(int, unsigned char);
static void write_number(int);
static void ins_int(int);
void i_generate_node(parse_node_t *);
static void i_generate_if_branch(parse_node_t *, int);
static void i_generate_loop(int, parse_node_t *, parse_node_t *,
		parse_node_t *);
static void i_update_branch_list(parse_node_t *, char *);
static int try_to_push(int, int);

static int foreach_depth = 0;

static int last_size_generated;
static int line_being_generated;

static int push_state;
static int push_start;

static parse_node_t *branch_list[3];
static int nforward_branches, nforward_branches_max;
static int *forward_branches = 0;

static void ins_real(double l)
{
	float f = (float)l;

	if (prog_code + 4 > prog_code_max) {
		mem_block_t *mbp = &mem_block[A_PROGRAM];

		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}
	STORE_FLOAT(prog_code, f);
}

/*
 * Store a 2 byte number. It is stored in such a way as to be sure
 * that correct byte order is used, regardless of machine architecture.
 * Also beware that some machines can't write a word to odd addresses.
 */
static void ins_short(short l)
{
	if (prog_code + 2 > prog_code_max) {
		mem_block_t *mbp = &mem_block[A_PROGRAM];
		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}
	STORE_SHORT(prog_code, l);
}

/*
 * Store a 4 byte number. It is stored in such a way as to be sure
 * that correct byte order is used, regardless of machine architecture.
 */
static void ins_int(int l)
{

	if (prog_code + 4 > prog_code_max) {
		mem_block_t *mbp = &mem_block[A_PROGRAM];
		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}
	STORE_INT(prog_code, l);
}

static void upd_short(int offset, int l, char *where)
{
	unsigned short s;

	IF_DEBUG(UPDATE_PROGRAM_SIZE);
	DEBUG_CHECK2(offset > CURRENT_PROGRAM_SIZE,
			"patch offset %x larger than current program size %x.\n",
			offset, CURRENT_PROGRAM_SIZE);
	if (l > USHRT_MAX) {
		char buf[256];

		sprintf(buf, "branch limit exceeded in %s, near line %i",
				where, line_being_generated);
		yyerror(buf);
	}
	s = l;

	COPY_SHORT(mem_block[A_PROGRAM].block + offset, &s);
}

static void ins_byte(unsigned char b)
{
	if (prog_code == prog_code_max) {
		mem_block_t *mbp = &mem_block[A_PROGRAM];
		UPDATE_PROGRAM_SIZE;
		realloc_mem_block(mbp);

		prog_code = mbp->block + mbp->current_size;
		prog_code_max = mbp->block + mbp->max_size;
	}
	*prog_code++ = b;
}

static void upd_byte(int offset, unsigned char b)
{
	IF_DEBUG(UPDATE_PROGRAM_SIZE);
	DEBUG_CHECK2(offset > CURRENT_PROGRAM_SIZE,
			"patch offset %x larger than current program size %x.\n",
			offset, CURRENT_PROGRAM_SIZE);
	mem_block[A_PROGRAM].block[offset] = b;
}

static void end_pushes(void) {
	if (push_state) {
		if (push_state > 1)
			upd_byte(push_start, push_state);
		push_state = 0;
	}
}

static void initialize_push(void) {
	int what = mem_block[A_PROGRAM].block[push_start];
	int arg = mem_block[A_PROGRAM].block[push_start + 1];

	prog_code = mem_block[A_PROGRAM].block + push_start;
	ins_byte(F_PUSH);
	push_start++; /* now points to the zero here */
	ins_byte(0);

	switch (what) {
		case F_CONST0:
			ins_byte(PUSH_NUMBER | 0);
			break;
		case F_CONST1:
			ins_byte(PUSH_NUMBER | 1);
			break;
		case F_BYTE:
			ins_byte(PUSH_NUMBER | arg);
			break;
		case F_SHORT_STRING:
			ins_byte(PUSH_STRING | arg);
			break;
		case F_LOCAL:
			ins_byte(PUSH_LOCAL | arg);
			break;
		case F_GLOBAL:
			ins_byte(PUSH_GLOBAL | arg);
			break;
	}
}

/*
 * Generate the code to push a number on the stack.
 * This varies since there are several opcodes (for
 * optimizing speed and/or size).
 */
static void write_small_number(int val) {
	if (try_to_push(PUSH_NUMBER, val)) return;
	ins_byte(F_BYTE);
	ins_byte(val);
}

static void write_number(int val)
{
	if ((val & ~0xff) == 0)
		write_small_number(val);
	else {
		end_pushes();
		if (val < 0 && val > -256) {
			ins_byte(F_NBYTE);
			ins_byte(-val);
		} else if (val >= -32768 && val <= 32767) {
			ins_byte(F_SHORT_INT);
			ins_short(val);
		} else {
			ins_byte(F_NUMBER);
			ins_int(val);
		}
	}
}

static void
generate_expr_list(parse_node_t *expr) {
	parse_node_t *pn;
	int n, flag;

	if (!expr) return;
	pn = expr;
	flag = n = 0;
	do {
		if (pn->type & 1) flag = 1;
		i_generate_node(pn->v.expr);
		n++;
	} while ((pn = pn->r.expr));

	if (flag) {
		pn = expr;
		do {
			n--;
			if (pn->type & 1) {
				end_pushes();
				ins_byte(F_EXPAND_VARARGS);
				ins_byte(n);
			}
		} while ((pn = pn->r.expr));
	}
}

static inline void
switch_to_line(int line) {
	int sz = CURRENT_PROGRAM_SIZE - last_size_generated;
	ADDRESS_TYPE s;
	unsigned char *p;

	if (sz) {
		s = line_being_generated;

		last_size_generated += sz;
		while (sz > 255) {
			p = (unsigned char *)allocate_in_mem_block(A_LINENUMBERS, sizeof(ADDRESS_TYPE) + 1);
			*p++ = 255;
#if !defined(USE_32BIT_ADDRESSES) && !defined(LPC_TO_C)
			STORE_SHORT(p, s);
#else
			STORE_INT(p, s);
#endif
			sz -= 255;
		}
		p = (unsigned char *)allocate_in_mem_block(A_LINENUMBERS, sizeof(ADDRESS_TYPE) + 1);
		*p++ = sz;
#if !defined(USE_32BIT_ADDRESSES) && !defined(LPC_TO_C)
		STORE_SHORT(p, s);
#else
		STORE_INT(p, s);
#endif
	}
	line_being_generated = line;
}

static int
try_to_push(int kind, int value) {
	if (push_state) {
		if (value <= PUSH_MASK) {
			if (push_state == 1)
				initialize_push();
			push_state++;
			ins_byte(kind | value);
			if (push_state == 255)
				end_pushes();
			return 1;
		} else end_pushes();
	} else if (value <= PUSH_MASK) {
		push_start = CURRENT_PROGRAM_SIZE;
		push_state = 1;
		switch (kind) {
			case PUSH_STRING: ins_byte(F_SHORT_STRING); break;
			case PUSH_LOCAL: ins_byte(F_LOCAL); break;
			case PUSH_GLOBAL: ins_byte(F_GLOBAL); break;
			case PUSH_NUMBER:
					  if (value == 0) {
						  ins_byte(F_CONST0);
						  return 1;
					  } else if (value == 1) {
						  ins_byte(F_CONST1);
						  return 1;
					  }
					  ins_byte(F_BYTE);
		}
		ins_byte(value);
		return 1;
	}
	return 0;
}

static short
generate(parse_node_t *node) {
    short where = CURRENT_PROGRAM_SIZE;

    if (num_parse_error) return 0;
    i_generate_node(node);
    free_tree();

    return where;
}

static short generate_function(function_t *f, parse_node_t *node, int num) {
    short ret;
    /* TODO
    if (pragmas & PRAGMA_OPTIMIZE) {
	optimizer_start_function(num);
	optimizer_state = 0;
	node = optimize(node);
	optimizer_end_function();
    }
    */
    ret = generate(node);
    return ret;
}

void
i_generate_node(parse_node_t *expr) {
	if (!expr) return;

	if (expr->line && expr->line != line_being_generated)
		switch_to_line(expr->line);
	switch (expr->kind) {
		case NODE_FUNCTION:
			{
				unsigned short num;

				/* In the following we know that this function wins all the */
				/* rest  - Sym                                              */

				num = FUNCTION_TEMP(expr->v.number)->u.index;
				FUNC(num)->address = generate_function(FUNC(num),
						expr->r.expr, expr->l.number);
				break;
			}
		case NODE_BINARY_OP:
			i_generate_node(expr->l.expr);
			/* fall through */
		case NODE_UNARY_OP:
			i_generate_node(expr->r.expr);
			/* fall through */
		case NODE_OPCODE:
			end_pushes();
			ins_byte(expr->v.number);
			break;
		case NODE_UNARY_OP_1:
			i_generate_node(expr->r.expr);
			/* fall through */
		case NODE_OPCODE_1:
			if (expr->v.number == F_LOCAL) {
				if (try_to_push(PUSH_LOCAL, expr->l.number)) break;
			} else if (expr->v.number == F_GLOBAL) {
				if (try_to_push(PUSH_GLOBAL, expr->l.number)) break;
			}
			end_pushes();
			ins_byte(expr->v.number);
			ins_byte(expr->l.number);
			break;
		case NODE_OPCODE_2:
			end_pushes();
			ins_byte(expr->v.number);
			ins_byte(expr->l.number);
			if (expr->v.number == F_LOOP_COND_NUMBER)
				ins_int(expr->r.number);
			else
				ins_byte(expr->r.number);
			break;
		case NODE_RETURN:
			{
				int n;
				n = foreach_depth;
				end_pushes();
				while (n--)
					ins_byte(F_EXIT_FOREACH);

				if (expr->r.expr) {
					i_generate_node(expr->r.expr);
					end_pushes();
					ins_byte(F_RETURN);
				} else ins_byte(F_RETURN_ZERO);
				break;
			}
		case NODE_STRING:
			if (try_to_push(PUSH_STRING, expr->v.number)) break;
			if (expr->v.number <= 0xff) {
				ins_byte(F_SHORT_STRING);
				ins_byte(expr->v.number);
			} else {
				ins_byte(F_STRING);
				ins_short(expr->v.number);
			}
			break;
		case NODE_REAL:
			end_pushes();
			ins_byte(F_REAL);
			ins_real(expr->v.real);
			break;
		case NODE_NUMBER:
			write_number(expr->v.number);
			break;
		case NODE_LAND_LOR:
			i_generate_node(expr->l.expr);
			i_generate_forward_branch(expr->v.number);
			i_generate_node(expr->r.expr);
			if (expr->l.expr->kind == NODE_BRANCH_LINK)
				i_update_forward_branch_links(expr->v.number,expr->l.expr);
			else
				i_update_forward_branch("&& or ||");
			break;
		case NODE_BRANCH_LINK:
			i_generate_node(expr->l.expr);
			end_pushes();
			ins_byte(0);
			expr->v.number = CURRENT_PROGRAM_SIZE;
			ins_short(0);
			i_generate_node(expr->r.expr);
			break;
		case NODE_CALL_1:
			generate_expr_list(expr->r.expr);
			end_pushes();
			if (expr->v.number == F_CALL_FUNCTION_BY_ADDRESS) {
				expr->l.number = comp_def_index_map[expr->l.number];
			}
			ins_byte(expr->v.number);
			ins_short(expr->l.number);
			ins_byte((expr->r.expr ? expr->r.expr->kind : 0));
			break;
		case NODE_CALL:
			generate_expr_list(expr->r.expr);
			end_pushes();
			ins_byte(expr->v.number);
			ins_short(expr->l.number);
			break;
		case NODE_TWO_VALUES:
			i_generate_node(expr->l.expr);
			i_generate_node(expr->r.expr);
			break;
		case NODE_CONTROL_JUMP:
			{
				int kind = expr->v.number;
				end_pushes();
				ins_byte(F_BRANCH);
				expr->v.expr = branch_list[kind];
				expr->l.number = CURRENT_PROGRAM_SIZE;
				ins_short(0);
				branch_list[kind] = expr;
				break;
			}
		case NODE_IF:
			i_generate_if_branch(expr->v.expr, 0);
			i_generate_node(expr->l.expr);
			if (expr->r.expr) {
				i_generate_else();
				i_generate_node(expr->r.expr);
			}
			i_update_forward_branch("if");
			break;
		case NODE_LOOP:
			i_generate_loop(expr->type, expr->v.expr, expr->l.expr, expr->r.expr);
			break;
		case NODE_FOREACH:
			{
				int tmp = 0;

				i_generate_node(expr->v.expr);
				end_pushes();
				ins_byte(F_FOREACH);
				if (expr->l.expr->v.number == F_GLOBAL_LVALUE)
					tmp |= FOREACH_RIGHT_GLOBAL;
				if (expr->r.expr) {
					if (tmp & FOREACH_RIGHT_GLOBAL)
						tmp = (tmp & ~FOREACH_RIGHT_GLOBAL) | FOREACH_LEFT_GLOBAL;

					tmp |= FOREACH_MAPPING;
					if (expr->r.expr->v.number == F_GLOBAL_LVALUE)
						tmp |= FOREACH_RIGHT_GLOBAL;
				}
				ins_byte(tmp);
				ins_byte(expr->l.expr->l.number);
				if (expr->r.expr)
					ins_byte(expr->r.expr->l.number);
			}
			break;
		case NODE_EFUN:
			{
				int novalue_used = expr->v.number & NOVALUE_USED_FLAG;
				int f = expr->v.number & ~NOVALUE_USED_FLAG;

				generate_expr_list(expr->r.expr);
				end_pushes();
				if (f < ONEARG_MAX) {
					ins_byte(f);
				} else {
					/* max_arg == -1 must use F_EFUNV so that varargs expansion works*/
					if (expr->l.number < 4 /* TODO: && instrs[f].max_arg != -1 */)
						ins_byte(F_EFUN0 + expr->l.number);
					else {
						ins_byte(F_EFUNV);
						ins_byte(expr->l.number);
					}
					ins_byte(f - ONEARG_MAX);
				}
				if (novalue_used) {
					/* the value of a void efun was used.  Put in a zero. */
					ins_byte(F_CONST0);
				}
				break;
				default:
				fatal("Unknown node %i in i_generate_node.\n", expr->kind);
			}
	}
}

static int
node_always_true(parse_node_t *node) {
    if (!node) return 1;
    if (node->kind == NODE_NUMBER)
	return node->v.number;
    return 0;
}

static int
generate_conditional_branch(parse_node_t *node) {
    int branch;

    if (!node)
	return F_BBRANCH;

    /* only have to handle while (x != 0) since while (x == 0) will be
     * handled by the x == 0 -> !x and !x optimizations.
     */
    if (IS_NODE(node, NODE_BINARY_OP, F_NE)) {
	if (IS_NODE(node->r.expr, NODE_NUMBER, 0))
	    node = node->l.expr;
	else if (IS_NODE(node->l.expr, NODE_NUMBER, 0))
	    node = node->r.expr;
    }
    if (IS_NODE(node, NODE_UNARY_OP, F_NOT)) {
	node = node->r.expr;
	branch = F_BBRANCH_WHEN_ZERO;
    } else {
	branch = F_BBRANCH_WHEN_NON_ZERO;
	if (node->kind == NODE_NUMBER) {
	    if (node->v.number == 0)
		branch = 0;
	    else
		branch = F_BBRANCH;
	    node = 0;
	}
	if (node) {
	    if (IS_NODE(node, NODE_BINARY_OP, F_LT)) {
		generate(node->l.expr);
		generate(node->r.expr);
		return F_BBRANCH_LT;
	    }
	}
    }
    generate(node);
    return branch;
}

static void i_generate_loop(int test_first, parse_node_t *block,
		parse_node_t *inc, parse_node_t *test) {
	parse_node_t *save_breaks = branch_list[CJ_BREAK];
	parse_node_t *save_continues = branch_list[CJ_CONTINUE];
	int forever = node_always_true(test);
	int pos;

	if (test_first == 2) foreach_depth++;
	branch_list[CJ_BREAK] = branch_list[CJ_CONTINUE] = 0;
	end_pushes();
	if (!forever && test_first)
		i_generate_forward_branch(F_BRANCH);
	pos = CURRENT_PROGRAM_SIZE;
	i_generate_node(block);
	i_update_branch_list(branch_list[CJ_CONTINUE], "continue");
	if (inc) i_generate_node(inc);
	if (!forever && test_first) i_update_forward_branch("loop");
	if (test && (test->v.number == F_LOOP_COND_LOCAL ||
				test->v.number == F_LOOP_COND_NUMBER ||
				test->v.number == F_NEXT_FOREACH)) {
		i_generate_node(test);
		ins_short(CURRENT_PROGRAM_SIZE - pos);
	} else i_branch_backwards(generate_conditional_branch(test), pos);
	i_update_branch_list(branch_list[CJ_BREAK], "break");
	branch_list[CJ_BREAK] = save_breaks;
	branch_list[CJ_CONTINUE] = save_continues;
	if (test_first == 2) foreach_depth--;
}

static void
i_generate_if_branch(parse_node_t *node, int invert) {
	int generate_both = 0;
	int branch = (invert ? F_BRANCH_WHEN_NON_ZERO : F_BRANCH_WHEN_ZERO);

	switch (node->kind) {
		case NODE_UNARY_OP:
			if (node->v.number == F_NOT) {
				i_generate_if_branch(node->r.expr, !invert);
				return;
			}
			break;
		case NODE_BINARY_OP:
			switch (node->v.number) {
				case F_EQ:
					generate_both = 1;
					branch = (invert ? F_BRANCH_EQ : F_BRANCH_NE);
					break;
				case F_GE:
					if (invert) {
						generate_both = 1;
						branch = F_BRANCH_GE;
					}
					break;
				case F_LE:
					if (invert) {
						generate_both = 1;
						branch = F_BRANCH_LE;
					}
					break;
				case F_LT:
					if (!invert) {
						generate_both = 1;
						branch = F_BRANCH_GE;
					}
					break;
				case F_GT:
					if (!invert) {
						generate_both = 1;
						branch = F_BRANCH_LE;
					}
					break;
				case F_NE:
					generate_both = 1;
					branch = (invert ? F_BRANCH_NE : F_BRANCH_EQ);
					break;
			}
	}
	if (generate_both) {
		i_generate_node(node->l.expr);
		i_generate_node(node->r.expr);
	} else {
		i_generate_node(node);
	}
	i_generate_forward_branch(branch);
}

void i_generate_forward_branch(char b) {
	end_pushes();
	ins_byte(b);
	if (nforward_branches == nforward_branches_max) {
		nforward_branches_max += 10;
		forward_branches = RESIZE(forward_branches, nforward_branches_max, int);
	}

	forward_branches[nforward_branches++] = CURRENT_PROGRAM_SIZE;
	ins_short(0);
}

void
i_update_forward_branch(char *what) {
	end_pushes();
	nforward_branches--;
	upd_short(forward_branches[nforward_branches],
			CURRENT_PROGRAM_SIZE - forward_branches[nforward_branches],
			what);
}

void i_update_forward_branch_links(char kind, parse_node_t *link_start) {
	int i;

	end_pushes();
	nforward_branches--;
	upd_short(forward_branches[nforward_branches],
			CURRENT_PROGRAM_SIZE - forward_branches[nforward_branches],
			"&& or ||");
	do {
		i = link_start->v.number;
		upd_byte(i-1, kind);
		upd_short(i, CURRENT_PROGRAM_SIZE - i, "&& or ||");
		link_start = link_start->l.expr;
	} while (link_start->kind == NODE_BRANCH_LINK);
}

void
i_branch_backwards(char b, int addr) {
	end_pushes();
	if (b) {
		ins_byte(b);
		ins_short(CURRENT_PROGRAM_SIZE - addr);
	}
}

static void
i_update_branch_list(parse_node_t *bl, char *what) {
	int current_size;

	end_pushes();
	current_size = CURRENT_PROGRAM_SIZE;

	while (bl) {
		upd_short(bl->l.number, current_size - bl->l.number, what);
		bl = bl->v.expr;
	}
}

void
i_generate_else() {
	int old;

	old = forward_branches[nforward_branches - 1];

	/* set up a new branch to after the end of the if */
	end_pushes();
	ins_byte(F_BRANCH);
	forward_branches[nforward_branches-1] = CURRENT_PROGRAM_SIZE;
	ins_short(0);

	/* update the old branch to point to this point */
	upd_short(old, CURRENT_PROGRAM_SIZE - old, "if");
}

void
i_initialize_parser() {
	foreach_depth = 0;

	if (!forward_branches) {
		forward_branches = CALLOCATE(10, int);
		nforward_branches_max = 10;
	}
	nforward_branches = 0;

	branch_list[CJ_BREAK] = 0;
	branch_list[CJ_CONTINUE] = 0;

	prog_code = mem_block[A_PROGRAM].block;
	prog_code_max = mem_block[A_PROGRAM].block + mem_block[A_PROGRAM].max_size;

	line_being_generated = 0;
	last_size_generated = 0;
}

void
i_uninitialize_parser() {
}

void
i_generate_final_program(int x) {
	if (!x) {
		UPDATE_PROGRAM_SIZE;
		/* This needs work
		 * if (pragmas & PRAGMA_OPTIMIZE)
		 *     optimize_icode(0, 0, 0);
		 */
		// TODO: save_file_info(current_file_id, current_line - current_line_saved);
		switch_to_line(-1); /* generate line numbers for the end */
	}
}

/* Currently, this procedure handles:
 * - jump threading
 */
void
optimize_icode(char *start, char *pc, char *end) {
	int instr;
	if (start == 0) {
		/* we don't optimize the initializer block right now b/c all the
		 * stuff we do (jump threading, etc) can't occur there.
		 */
		start = mem_block[A_PROGRAM].block;
		pc = start;
		end = pc + mem_block[A_PROGRAM].current_size;
		if (*pc == 0) {
			/* no initializer jump */
			pc += 3;
		}
	}
	while (pc < end) {
		switch (instr = EXTRACT_UCHAR(pc++)) {
			case F_NUMBER:
			case F_REAL:
			case F_CALL_INHERITED:
				pc += 4;
				break;
			case F_SIMUL_EFUN:
			case F_CALL_FUNCTION_BY_ADDRESS:
				pc += 3;
				break;
			case F_BRANCH:
			case F_BRANCH_WHEN_ZERO:
			case F_BRANCH_WHEN_NON_ZERO:
			case F_BBRANCH:
			case F_BBRANCH_WHEN_ZERO:
			case F_BBRANCH_WHEN_NON_ZERO:
				{
					char *tmp;
					short sarg;
					/* thread jumps */
					COPY_SHORT(&sarg, pc);
					if (instr > F_BRANCH)
						tmp = pc - sarg;
					else
						tmp = pc + sarg;
					sarg = 0;
					while (1) {
						if (EXTRACT_UCHAR(tmp) == F_BRANCH) {
							COPY_SHORT(&sarg, tmp + 1);
							tmp += sarg + 1;
						} else if (EXTRACT_UCHAR(tmp) == F_BBRANCH) {
							COPY_SHORT(&sarg, tmp + 1);
							tmp -= sarg - 1;
						} else break;
					}
					if (!sarg) {
						pc += 2;
						break;
					}
					/* be careful; in the process of threading a forward jump
					 * may have changed to a reverse one or vice versa
					 */
					if (tmp > pc) {
						if (instr > F_BRANCH) {
							pc[-1] -= 3;   /* change to forward branch */
						}
						sarg = tmp - pc;
					} else {
						if (instr <= F_BRANCH) {
							pc[-1] += 3;   /* change to backwards branch */
						}
						sarg = pc - tmp;
					}
					STORE_SHORT(pc, sarg);
					break;
				}
#ifdef F_LOR
			case F_LOR:
			case F_LAND:
				{
					char *tmp;
					short sarg;
					/* thread jumps */
					COPY_SHORT(&sarg, pc);
					tmp = pc + sarg;
					sarg = 0;
					while (1) {
						if (EXTRACT_UCHAR(tmp) == F_BRANCH) {
							COPY_SHORT(&sarg, tmp + 1);
							tmp += sarg + 1;
						} else if (EXTRACT_UCHAR(tmp) == F_BBRANCH) {
							COPY_SHORT(&sarg, tmp + 1);
							tmp -= sarg - 1;
						} else break;
					}
					if (!sarg) {
						pc += 2;
						break;
					}
					/* be careful; in the process of threading a forward jump
					 * may have changed to a reverse one or vice versa
					 */
					if (tmp > pc) {
						sarg = tmp - pc;
					} else {
#ifdef DEBUG
						fprintf(stderr,"Optimization failed; can't || or && backwards.\n");
#endif
						pc += 2;
						break;
					}
					STORE_SHORT(pc, sarg);
					break;
				}
#endif
			case F_AGGREGATE:
			case F_AGGREGATE_ASSOC:
			case F_STRING:
#ifdef F_JUMP_WHEN_ZERO
			case F_JUMP_WHEN_ZERO:
			case F_JUMP_WHEN_NON_ZERO:
#endif
#ifdef F_JUMP
			case F_JUMP:
#endif
				pc += 2;
				break;
			case F_GLOBAL_LVALUE:
			case F_GLOBAL:
			case F_SHORT_STRING:
			case F_LOCAL:
			case F_LOCAL_LVALUE:
			case F_BYTE:
			case F_NBYTE:
				pc++;
				break;
			case F_EFUN0:
			case F_EFUN1:
			case F_EFUN2:
			case F_EFUN3:
			case F_EFUNV:
				instr = EXTRACT_UCHAR(pc++) + ONEARG_MAX;
			default:
				if ((instr >= BASE)/* TODO: &&
						(instrs[instr].min_arg != instrs[instr].max_arg)*/)
					pc++;
		}
	}
}
